﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

//------------------------------------------------------------
// shaco Framework
// Copyright © 2017-2021 chang.liu All rights reserved.
// Feedback: 449612236@qq.com
//------------------------------------------------------------

namespace shaco
{
    public class UnityObjectAutoReleaseComponent : MonoBehaviour
    {
        public class AutoReleaseInfo : shaco.Base.IObjectPoolData
        {
            public class CallbackInfo : shaco.Base.IObjectPoolData
            {
                public string displayName = null;
                public System.Action callback = null;
                public shaco.Base.StackLocation stackLocationBind = null;

                public void OnRecycle()
                {
                    displayName = null;
                    callback = null;
                    stackLocationBind = null;
                }
            }
            public object target;
            public List<CallbackInfo> callbacks = new List<CallbackInfo>();

            public void OnRecycle()
            {
                target = null;
                callbacks.Clear();
            }
        }

        public ICollection<AutoReleaseInfo> willAutoReleaseInfos { get { return _onDestroyCallBack.Values; } }

        private Dictionary<object, AutoReleaseInfo> _onDestroyCallBack = new Dictionary<object, AutoReleaseInfo>();
        private bool _shouldCheckReStartEvent = true;

        void OnEnable() 
        {
            _shouldCheckReStartEvent = false;
        }

        //绑定销毁事件
        public void AddOnDestroyCallBack(object target, System.Action callback, string displayName = null)
        {
            if (null == target)
                return;

            if (_shouldCheckReStartEvent)
            {
                _shouldCheckReStartEvent = false;
                CheckReStartEvent();
            }

            AutoReleaseInfo findValue = null;
            if (!_onDestroyCallBack.TryGetValue(target, out findValue))
            {
                findValue = shaco.GameHelper.objectpool.Instantiate(() => new AutoReleaseInfo());
                findValue.target = target;
                _onDestroyCallBack.Add(target, findValue);
            }

            var newCallBackInfo = shaco.GameHelper.objectpool.Instantiate(() => new AutoReleaseInfo.CallbackInfo());
            newCallBackInfo.displayName = displayName;
            newCallBackInfo.callback = callback;

#if DEBUG_LOG
            if (null == newCallBackInfo.stackLocationBind)
                newCallBackInfo.stackLocationBind = new shaco.Base.StackLocation();
            newCallBackInfo.stackLocationBind.GetStack();
#endif
            findValue.callbacks.Add(newCallBackInfo);
        }

        //移除销毁事件
        public void RemoveOnDestroyCallBacks(object target)
        {
            AutoReleaseInfo findValue = null;
            if (!_onDestroyCallBack.TryGetValue(target, out findValue))
            {
                shaco.Log.Error("UnityObjectAutoReleaseComponent RemoveOnDestroyCallBacks error: not found target=" + target, this);
                return;
            }

            for (int i = findValue.callbacks.Count - 1; i >= 0; --i)
            {
                findValue.callbacks[i].RecyclingWithPool();
            }
            findValue.RecyclingWithPool();

            _onDestroyCallBack.Remove(target);
            CheckAutoDestroyMeWhenCallBackEmpty();
        }

        //移除销毁事件
        public void RemoveOnDestroyCallBack(object target, System.Action callback)
        {
            AutoReleaseInfo findValue = null;
            if (!_onDestroyCallBack.TryGetValue(target, out findValue))
            {
                shaco.Log.Error("UnityObjectAutoReleaseComponent RemoveOnDestroyCallBack error: not found 1 target=" + target + " callback=" + callback, this);
                return;
            }

            bool removed = false;
            for (int i = findValue.callbacks.Count - 1; i >= 0; --i)
            {
                if (findValue.callbacks[i].callback == callback)
                {
                    findValue.callbacks[i].RecyclingWithPool();
                    findValue.callbacks.RemoveAt(i);
                    removed = true;
                    break;
                }
            }
            if (!removed)
            {
                shaco.Log.Error("UnityObjectAutoReleaseComponent RemoveOnDestroyCallBack error: not found 2 target=" + target + " callback=" + callback, this);
                return;
            }

            if (findValue.callbacks.Count == 0)
            {
                findValue.RecyclingWithPool();
                _onDestroyCallBack.Remove(target);
                CheckAutoDestroyMeWhenCallBackEmpty();
            }
        }

        void OnApplicationQuit()
        {
            _onDestroyCallBack.Clear();
        }

        //如果该组件没有执行过Start方法，则需要主动触发一次，以防止OnDestroy也不触发
        void CheckReStartEvent()
        {
            shaco.Log.Warning("UnityObjectAutoReleaseComponent CheckReStartEvent warning: self gameObject is inactive and nerver call Awake() or Start(), this=" + this, this);

            var shouldReactiveParents = new Queue<Transform>();
            var parent = this.transform;
            while (null != parent)
            {
                if (!parent.gameObject.activeSelf)
                    shouldReactiveParents.Enqueue(parent);
                parent = parent.parent;
            }

            foreach (var iter in shouldReactiveParents)
            {
                iter.gameObject.SetActive(true);
            }
            foreach (var iter in shouldReactiveParents)
            {
                iter.gameObject.SetActive(false);
            }
        }

        void OnDestroy()
        {
            if (_onDestroyCallBack.IsNullOrEmpty())
                return;

            var objectpool = shaco.GameHelper.objectpool;
            foreach (var iter in _onDestroyCallBack)
            {
                for (int i = 0; i < iter.Value.callbacks.Count; ++i)
                {
                    var callbackTmp = iter.Value.callbacks[i];
                    try
                    {
                        callbackTmp.callback();

                        if (objectpool.IsInstantiated(callbackTmp))
                            callbackTmp.RecyclingWithPool();
                    }
                    catch (System.Exception e)
                    {
                        var callbackSubTmp = callbackTmp.callback;
                        Log.Error("UnityObjectAutoReleaseComponent OnDestroy error: Target=" + callbackSubTmp.Target + " Method=" + callbackSubTmp.Method + " e=" + e, this);
                    }
                }
                if (objectpool.IsInstantiated(iter.Value))
                    iter.Value.RecyclingWithPool();
            }
            _onDestroyCallBack.Clear();
        }

        private void CheckAutoDestroyMeWhenCallBackEmpty()
        {
            //如果当前组件没有需要绑定的对象则销毁自己
            if (_onDestroyCallBack.IsNullOrEmpty())
            {
                MonoBehaviour.Destroy(this);
            }
        }
    }
}